/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.client;

import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.creativor.rayson.api.CallFuture;
import org.creativor.rayson.common.Invocation;
import org.creativor.rayson.common.InvocationResultType;
import org.creativor.rayson.common.RpcCallException;
import org.creativor.rayson.common.Stream;
import org.creativor.rayson.exception.CallExecutionException;
import org.creativor.rayson.exception.ReadInvocationException;
import org.creativor.rayson.exception.RpcException;
import org.creativor.rayson.exception.UnsupportedVersionException;
import org.creativor.rayson.transport.common.ConnectionProtocol;
import org.creativor.rayson.transport.common.Packet;
import org.creativor.rayson.transport.common.PacketException;

// TODO: Auto-generated Javadoc
/**
 * It presents one RPC call in cilent side.
 * 
 * @param <V>
 *            The result type of this call.
 * @author Nick Zhang
 */
public final class ClientCall<V>
{
	private static final int BUFFER_SIZE = 1024;
	private static final AtomicLong UID = new AtomicLong(0);
	private static final byte[] SESSION_RESERVED_DATA =
			new byte[ConnectionProtocol.SESSION_RESERVED_DATA_LENGTH];
	private CallFutureImpl<V> future;
	private long id;
	private Invocation invocation;
	private Packet requestPacket;
	private ProxySession session;
	
	/**
	 * Instantiates a new client call.
	 * 
	 * @param session
	 *            the session
	 * @param invocation
	 *            the invocation
	 * @param future
	 *            the future
	 * @throws PacketException
	 *             the packet exception
	 */
	ClientCall(ProxySession session, Invocation invocation, CallFutureImpl<V> future)
			throws PacketException
	{
		this.id = UID.getAndIncrement();
		this.session = session;
		// update session last accessed time
		session.doLastAccess();
		this.invocation = invocation;
		this.future = future;
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(BUFFER_SIZE);
		DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
		try
		{
			dataOutputStream.writeLong(id);
			session.write(dataOutputStream);
			dataOutputStream.write(SESSION_RESERVED_DATA);
			invocation.write(dataOutputStream);
			requestPacket = new Packet();
		} catch (IOException e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * Gets the id.
	 * 
	 * @return the id
	 */
	public long getId()
	{
		return id;
	}
	
	/**
	 * Gets the invocation.
	 * 
	 * @return the invocation
	 */
	public Invocation getInvocation()
	{
		return invocation;
	}
	
	/**
	 * Gets the request packet.
	 * 
	 * @return the request packet
	 */
	public Packet getRequestPacket()
	{
		return this.requestPacket;
	}
	
	/**
	 * Gets the future.
	 * 
	 * @return the future
	 */
	public CallFuture<V> getFuture()
	{
		return future;
	}
	
	/**
	 * Gets the result.
	 * 
	 * @return the result
	 * @throws InterruptedException
	 *             the interrupted exception
	 * @throws RpcException
	 *             the rpc exception
	 * @throws CallExecutionException
	 *             the call execution exception
	 */
	public V getResult() throws InterruptedException, RpcException, CallExecutionException
	{
		return future.get();
	}
	
	/**
	 * Notify connection closed.
	 */
	public void notifyConnectionClosed()
	{
		this.future.setException(new RpcCallException(new ConnectionClosedException()));
	}
	
	/**
	 * Read result.
	 * 
	 * @param in
	 *            the in
	 */
	public void readResult(DataInput in)
	{
		InvocationResultType resultType;
		try
		{
			resultType = InvocationResultType.valueOf(in.readByte());
			
			switch (resultType)
			{
				case SUCCESSFUL:
					this.future.set((V) Stream.readPortable(in));
					break;
				case EXCEPTION:
					RpcCallException remoteExceptionHandler = new RpcCallException();
					remoteExceptionHandler.read(in);
					if (remoteExceptionHandler.getCause() instanceof UnsupportedVersionException)
						session.getUnsupportedVersionException(remoteExceptionHandler.getCause()
								.getMessage());
					this.future.setException(remoteExceptionHandler);
					break;
				default:
					break;
			}
		} catch (IOException e)
		{
			this.future.setException(new RpcCallException(new ReadInvocationException(
					"Read call result error:" + e.getMessage())));
		}
	}
	
	/**
	 * To string.
	 * 
	 * @return the string
	 */
	@Override
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append("{");
		sb.append("id: ");
		sb.append(id);
		sb.append(", session: ");
		sb.append(this.session.toString());
		sb.append(", invocation: ");
		sb.append(this.invocation.toString());
		sb.append("}");
		return sb.toString();
	}
}
